/**
 * 所有版权归 广西梧州 陈锦韬 所有
 *
 * @Title: CamundaServiceImpl
 * @Package com.rx.camunda.service
 * @Description: 流程服务
 * @author: 陈锦韬
 * @date: 2021\8\21 0021
 * @version V1.0
 * @Copyright: 2021 陈锦韬  All rights reserved.
 */
package com.rx.camunda.service;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.rx.camunda.enm.FLOW_STATE;
import com.rx.camunda.vo.*;
import com.rx.camunda.vo.iface.ExecutionFlowInfo;
import com.rx.core.base.RxBaseData;
import com.rx.core.enm.RESULT_CODE;
import com.rx.core.enm.RX_LOG_MODE;
import com.rx.core.service.RxBaseAbstractService;
import com.rx.core.util.EmptyChecker;
import com.rx.core.util.RxContentHolder;
import com.rx.core.util.RxUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.camunda.bpm.engine.*;
import org.camunda.bpm.engine.history.HistoricProcessInstance;
import org.camunda.bpm.engine.history.HistoricProcessInstanceQuery;
import org.camunda.bpm.engine.history.HistoricTaskInstance;
import org.camunda.bpm.engine.history.HistoricTaskInstanceQuery;
import org.camunda.bpm.engine.impl.TaskServiceImpl;
import org.camunda.bpm.engine.impl.persistence.entity.*;
import org.camunda.bpm.engine.repository.DeploymentBuilder;
import org.camunda.bpm.engine.repository.ProcessDefinition;
import org.camunda.bpm.engine.repository.ProcessDefinitionQuery;
import org.camunda.bpm.engine.runtime.ProcessInstance;
import org.camunda.bpm.engine.runtime.ProcessInstanceQuery;
import org.camunda.bpm.engine.task.Task;
import org.camunda.bpm.engine.task.TaskQuery;
import org.camunda.bpm.engine.variable.Variables;
import org.camunda.bpm.model.bpmn.BpmnModelInstance;
import org.camunda.bpm.model.bpmn.Query;
import org.camunda.bpm.model.bpmn.instance.ExclusiveGateway;
import org.camunda.bpm.model.bpmn.instance.FlowNode;
import org.camunda.bpm.model.bpmn.instance.UserTask;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author: Administrator
 * @Description: 流程服务
 * @date: 2021\8\21 0021
 */
@Slf4j
@Service
public class CamundaServiceImpl extends RxBaseAbstractService implements CamundaService{

    /**
     * 流程部署
     */
    @Autowired
    RepositoryService repositoryService;

    @Autowired
    RuntimeService runtimeService;

    @Autowired
    TaskService taskService;

    @Autowired
    HistoryService historyService;
    @Autowired
    IdentityService identityService;

    public Page createPage(long count, RxBaseData rxBaseData){
        Page page = new Page<>(rxBaseData.getPageNum(), rxBaseData.getPageSize());
        page.setTotal(count);
        if (count % rxBaseData.getPageSize() > 0){
            page.setPages(count/rxBaseData.getPageSize() + 1);
        }else {
            page.setPages(count/rxBaseData.getPageSize());
        }
        return page;
    }


    @Override
    public Object processDefinedPage(ActReProcdef data) {

        int maxResults = data.getPageSize();
        int firstPage = maxResults * (data.getPageNum()-1);

        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        // 当前端指定不是最新版本。
        if (!Integer.valueOf(0).equals(data.getReturnType())){
            processDefinitionQuery.latestVersion();
        }
        if (EmptyChecker.notEmpty(data.getDefinitionId())){
            processDefinitionQuery.processDefinitionKeyLike("%" + data.getDefinitionId()+ "%");
        }
        if (EmptyChecker.notEmpty(data.getBid())){
            processDefinitionQuery.processDefinitionId( data.getBid());
        }
        if (EmptyChecker.notEmpty(data.getName())){
            processDefinitionQuery.processDefinitionNameLike("%" +data.getName() + "%" );
        }
        long count = processDefinitionQuery.count();
        Page page = createPage(count,data);
        if (count < 1){
            return page;
        }

        List<ProcessDefinition> list = processDefinitionQuery.listPage(firstPage,maxResults);
        if (EmptyChecker.isEmpty(list)){
            return page;
        }


        List<ActReProcdef> resultList = new ArrayList<>();
        for (ProcessDefinition processDefinition : list) {
            ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity)processDefinition;
            ActReProcdef actReProcdef = new ActReProcdef();
            RxUtil.copyProperties(processDefinition,actReProcdef);
            actReProcdef.setBid(processDefinition.getId());
            actReProcdef.setVer(processDefinitionEntity.getRevision());
            actReProcdef.setDocument(processDefinition.getDescription());
            actReProcdef.setDefinitionId(processDefinition.getKey());
            resultList.add(actReProcdef);
        }

        page.setRecords(resultList);
        return page;
    }

    @Override
    public Object processDefinedState(List<ActReProcdef> dataList) {
        RxUtil.CheckUtil.check(dataList,RESULT_CODE.EMPTY,"请选择流程");
        for (ActReProcdef data : dataList) {
            RxUtil.CheckUtil.condition(EmptyChecker.isEmpty(data.getDefinitionId())&&EmptyChecker.isEmpty(data.getBid()),
                    RESULT_CODE.EMPTY,"流程定义Id不能为空");

            if (FLOW_STATE.SUSPENSION.getCode().equals(data.getSuspensionState())){
                if (EmptyChecker.isEmpty(data.getDefinitionId())){
                    repositoryService.suspendProcessDefinitionById(data.getBid());
                }else {
                    repositoryService.suspendProcessDefinitionByKey(data.getDefinitionId());
                }
            }else {
                if (EmptyChecker.isEmpty(data.getDefinitionId())){
                    repositoryService.activateProcessDefinitionById(data.getBid());
                }else {
                    repositoryService.activateProcessDefinitionByKey(data.getDefinitionId());
                }
            }
        }


        return Integer.valueOf(0);
    }

    @Override
    public Object processDefinedDelete(ActReProcdef data) {
        RxUtil.CheckUtil.check(data.getBid(),RESULT_CODE.EMPTY,"请选择删除流程版本");
        repositoryService.deleteProcessDefinition(data.getBid(),true);
        return Integer.valueOf(0);
    }

    @Override
    public Object processDefinedReDeploy(ActReProcdef data) {
        RxUtil.CheckUtil.check(data.getResourceName(),RESULT_CODE.EMPTY,"请选择重新部署的流程版本");
        RxUtil.CheckUtil.check(data.getDeploymentId(),RESULT_CODE.EMPTY,"请选择重新部署的流程版本");
        DeploymentBuilder deployment = repositoryService.createDeployment();
        deployment.addDeploymentResourceByName(data.getDeploymentId(),data.getResourceName());
        deployment.deploy();
        return Integer.valueOf(0);
    }

    @Override
    public Object processDefinedDownload(ActReProcdef data) {

        // 获取文件
        InputStream processModel = repositoryService.getProcessModel(data.getBid());
        File file = new File(data.getName() + "_" + data.getVersion() + ".bpmn");
        try {
            FileUtils.copyInputStreamToFile(processModel,file);
            return RxUtil.FileUtil.export(file);
        } catch (IOException e) {
            RxUtil.CheckUtil.throwOut(RESULT_CODE.FAIL,"文件流失败");
        }
        return null;
    }

    @Override
    public Object processDefinedUpload(MultipartFile file) {

        try {
            InputStream inputStream = new ByteArrayInputStream(file.getBytes());
            DeploymentBuilder deployment = repositoryService.createDeployment();
            deployment.addInputStream(file.getOriginalFilename(),inputStream).deploy();
        } catch (IOException e) {
            log.error(RX_LOG_MODE.ERROR.getFormat("上传失败 ：{}"),e.getMessage());
        }
        return Integer.valueOf(0);
    }

    @Override
    public Object processDefinedTaskPage(ActRuTask data) {
        BpmnModelInstance bpmnModelInstance = repositoryService.getBpmnModelInstance(data.getProcessDefinitionId());
        Collection<UserTask> modelElementsByType = bpmnModelInstance.getModelElementsByType(UserTask.class);
        if(EmptyChecker.isEmpty(modelElementsByType)){
            return Collections.emptyList();
        }
        List<ActRuTask> resultList = new ArrayList<>();
        for (UserTask userTask : modelElementsByType) {
            ActRuTask actRuTask = new ActRuTask();
            actRuTask.setTaskDefKey(userTask.getId());
            actRuTask.setName(userTask.getName());
            resultList.add(actRuTask);
        }
        return resultList;
    }

    private Map<String,List<ProcessDefinition>> getProcessDefInfo(List<String> definedList){
        String[] strings = new String[definedList.size()];
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionIdIn(definedList.toArray(strings)).list();
        return list.stream().collect(Collectors.groupingBy(e->e.getId()));
    }

    @Override
    public Object processInstancePage(ActRuInstance data) {
        int maxResults = data.getPageSize();
        int firstPage = maxResults * (data.getPageNum()-1);
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        long count = processInstanceQuery.count();
        Page page = createPage(count,data);
        if (count < 1){
            return page;
        }
        List<ProcessInstance> processInstances = processInstanceQuery.listPage(firstPage, maxResults);
        if (EmptyChecker.isEmpty(processInstances)){
            return page;
        }

        List<String> definedList = processInstances.stream().map(ProcessInstance::getProcessDefinitionId).collect(Collectors.toList());
        Map<String,List<ProcessDefinition>> map = getProcessDefInfo(definedList);

        List<ActRuInstance> resultList = new ArrayList<>();
        for (ProcessInstance processInstance : processInstances) {
            ExecutionEntity executionEntity = (ExecutionEntity)processInstance;
            ActRuInstance actRuInstance = new ActRuInstance();
            RxUtil.copyProperties(executionEntity,actRuInstance);
            actRuInstance.setBid(processInstance.getId());
            actRuInstance.setVer(executionEntity.getRevision());
            List<ProcessDefinition> processDefinitions = map.get(processInstance.getProcessDefinitionId());
            if (EmptyChecker.notEmpty(processDefinitions)){
                ProcessDefinitionEntity entity = (ProcessDefinitionEntity)RxUtil.ListUtil.getFirst(processDefinitions);
                actRuInstance.setProcessDefinitionName(entity.getName());
                actRuInstance.setVersionTag(entity.getVersionTag());
                actRuInstance.setVersion(entity.getVersion());
            }
            resultList.add(actRuInstance);
        }

        page.setRecords(resultList);
        return page;
    }

    private void  assignInstanceInfo(ExecutionFlowInfo info,String defId,Map<String,List<ProcessDefinition>> map){
        List<ProcessDefinition> processDefinitions = map.get(defId);
        if (EmptyChecker.notEmpty(processDefinitions)){
            ProcessDefinitionEntity entity = (ProcessDefinitionEntity)RxUtil.ListUtil.getFirst(processDefinitions);
            info.assignInstanceName(entity.getName());
            info.assignInstanceVersionTag(entity.getVersionTag());
            info.assignInstanceVersion(entity.getVersion());
        }
    }

    @Override
    public Object processTaskPage(ActRuTask data) {
        int maxResults = data.getPageSize();
        int firstPage = maxResults * (data.getPageNum()-1);

        TaskQuery taskQuery = taskService.createTaskQuery();

        if (EmptyChecker.notEmpty(data.getProcessInstanceId())){
            taskQuery.processInstanceId(data.getProcessInstanceId());
        }

        long count = taskQuery.count();
        Page page = createPage(count,data);
        if (count < 1){
            return page;
        }

        List<Task> tasks = taskQuery.listPage(firstPage, maxResults);
        if (EmptyChecker.isEmpty(tasks)){
            return page;
        }

        List<String> definedList = tasks.stream().map(Task::getProcessDefinitionId).collect(Collectors.toList());
        Map<String,List<ProcessDefinition>> map = getProcessDefInfo(definedList);

        List<ActRuTask> resultList = new ArrayList<>();
        for (Task task : tasks) {
            TaskEntity taskEntity = (TaskEntity)task;
            ActRuTask actRuInstance = new ActRuTask();
            RxUtil.copyProperties(taskEntity,actRuInstance);
            actRuInstance.setBid(task.getId());
            actRuInstance.setVer(taskEntity.getRevision());
            actRuInstance.setDueDate(taskEntity.getDueDate());
            actRuInstance.setParentTaskId(taskEntity.getParentTaskId());
            actRuInstance.setTaskDefKey(taskEntity.getTaskDefinitionKey());
            assignInstanceInfo(actRuInstance,taskEntity.getProcessDefinitionId(),map);
            resultList.add(actRuInstance);
        }

        page.setRecords(resultList);
        return page;
    }

    @Override
    public Object processPass(ActRuOperate data) {
        identityService.setAuthenticatedUserId(RxContentHolder.getUserInfo().getBid());
        if (EmptyChecker.isEmpty(data.getOperateList())){
            taskService.complete(data.getTaskId(), Variables.putValue("reason_" + data.getTaskId(),data.getReason()));
        }else {
            for (ActRuOperate actRuOperate : data.getOperateList()) {
                taskService.complete(actRuOperate.getTaskId(), Variables.putValue("reason_" + actRuOperate.getTaskId(),data.getReason()));
            }
        }
//        TaskServiceImpl service = (TaskServiceImpl)taskService;
//        service.getCommandExecutor()
        return Integer.valueOf(0);
    }

    @Override
    public Object processReject(ActRuOperate data) {
        if (EmptyChecker.isEmpty(data.getOperateList())){
            runtimeService.deleteProcessInstance(data.getProcessInstanceId(),data.getReason(),true,true);
        }else {
            List<String> collect = data.getOperateList().stream().map(ActRuOperate::getProcessInstanceId).collect(Collectors.toList());
            runtimeService.deleteProcessInstances(collect,data.getReason(),true,true);
        }

        return Integer.valueOf(0);
    }

    private List<UserTask> eachUserTask(FlowNode flowNode,boolean back){
        Query<FlowNode> nodes = null;
        if (back){
            nodes = flowNode.getSucceedingNodes();
        }else{
            nodes = flowNode.getPreviousNodes();
        }
        int count = nodes.count();
        if (count < 1) {
            return new ArrayList<>();
        }
        List<UserTask> reslut = new ArrayList<>();
        if (count == 1){
            FlowNode flowNodeChild = nodes.singleResult();
            if (flowNodeChild instanceof UserTask){
                reslut.add((UserTask) flowNodeChild);
                return reslut;
            }else {
                // TODO: 非排他网关需要慎重
                return eachUserTask(flowNodeChild,back);
            }
        }
        // TODO: 多条回路需要慎重。一般是网关才有多条回路
        for (FlowNode node : nodes.list()) {
            if (node instanceof UserTask){
                reslut.add((UserTask) node);
            }else {
                reslut.addAll(eachUserTask(node,back));
            }
        }
        return reslut;
    }
    private List<String> preUserTask(BpmnModelInstance bpmnModel,String taskId,boolean back){
        Collection<UserTask> userTaskCollection = bpmnModel.getModelElementsByType(UserTask.class);
        for (UserTask userTask : userTaskCollection) {
            if (userTask.getId().equalsIgnoreCase(taskId)){
                List<UserTask> list = eachUserTask(userTask,back);
                return list.stream().map(UserTask::getId).collect(Collectors.toList());
            }
        }

        return Collections.emptyList();
    }

    private void procesRefuseByBpmn(BpmnModelInstance bpmnModel,ActRuOperate data){
        List<String> taskIdList = preUserTask(bpmnModel, data.getTaskId(),false);
        for (String taskId : taskIdList) {
            runtimeService.createModification(data.getProcessDefinitionId())
                    .startBeforeActivity(taskId)
                    .processInstanceIds(data.getProcessInstanceId())
                    .cancelAllForActivity(data.getTaskId(),true)
                    .execute();
        }
    }

    private Map<String,BpmnModelInstance> getBpmnByDefId(ActRuOperate data) {
        Map<String,BpmnModelInstance > bpmnMap = new HashMap<>();
        List<String> collect = data.getOperateList().stream().map(ActRuOperate::getProcessDefinitionId).collect(Collectors.toList());
        for (String instanceId : collect) {
            BpmnModelInstance bpmnModel = repositoryService.getBpmnModelInstance(instanceId);
            bpmnMap.put(instanceId,bpmnModel);
        }
        return bpmnMap;
    }
    @Override
    public Object processRefuse(ActRuOperate data) {

        if (EmptyChecker.isEmpty(data.getOperateList())){
            BpmnModelInstance bpmnModel = repositoryService.getBpmnModelInstance(data.getProcessDefinitionId());
            procesRefuseByBpmn(bpmnModel,data);

        }else {
            Map<String,BpmnModelInstance > bpmnMap = getBpmnByDefId(data);
            for (ActRuOperate actRuOperate : data.getOperateList()) {
                BpmnModelInstance bpmnModelInstance = bpmnMap.get(actRuOperate.getProcessDefinitionId());
                procesRefuseByBpmn(bpmnModelInstance,actRuOperate);
            }
        }

        return Integer.valueOf(0);
    }

    private void procesCancelByBpmn(BpmnModelInstance bpmnModel,ActRuOperate data){
        List<String> taskIdList = preUserTask(bpmnModel, data.getTaskId(),true);
        for (String taskId : taskIdList) {
            runtimeService.createModification(data.getProcessDefinitionId())
                    .startBeforeActivity(data.getTaskId())
                    .processInstanceIds(data.getProcessInstanceId())
                    .cancelAllForActivity(taskId,true)
                    .execute();
        }
    }

    @Override
    public Object processCancel(ActRuOperate data) {
        if (EmptyChecker.isEmpty(data.getOperateList())){
            BpmnModelInstance bpmnModel = repositoryService.getBpmnModelInstance(data.getProcessDefinitionId());
            procesCancelByBpmn(bpmnModel,data);

        }else {
            Map<String,BpmnModelInstance > bpmnMap = getBpmnByDefId(data);
            for (ActRuOperate actRuOperate : data.getOperateList()) {
                BpmnModelInstance bpmnModelInstance = bpmnMap.get(actRuOperate.getProcessDefinitionId());
                procesCancelByBpmn(bpmnModelInstance,actRuOperate);
            }
        }

        return Integer.valueOf(0);
    }

    @Override
    public Object processRedo(ActRuOperate data) {

        if (EmptyChecker.isEmpty(data.getOperateList())){
            runtimeService.restartProcessInstances(data.getProcessDefinitionId())
                    .startBeforeActivity(data.getTaskId())
                    .processInstanceIds(data.getProcessInstanceId())
                    .execute();
        }else {
            for (ActRuOperate actRuOperate : data.getOperateList()) {
                runtimeService.restartProcessInstances(actRuOperate.getProcessDefinitionId())
                        .startBeforeActivity(actRuOperate.getTaskId())
                        .processInstanceIds(actRuOperate.getProcessInstanceId())
                        .execute();
            }
        }

        return Integer.valueOf(0);
    }

    @Override
    public Object processDonePage(ActRuInstance data) {
        int maxResults = data.getPageSize();
        int firstPage = maxResults * (data.getPageNum()-1);
        HistoricTaskInstanceQuery historicTaskInstanceQuery = historyService.createHistoricTaskInstanceQuery()
                .taskAssignee(RxContentHolder.getUserInfo().getBid());

        long count = historicTaskInstanceQuery.count();
        Page page = createPage(count,data);
        if (count < 1){
            return page;
        }
        List<HistoricTaskInstance> historicProcessInstances = historicTaskInstanceQuery.listPage(firstPage, maxResults);
        if (EmptyChecker.isEmpty(historicProcessInstances)){
            return page;
        }

        List<String> definedList = historicProcessInstances.stream().map(HistoricTaskInstance::getProcessDefinitionId).collect(Collectors.toList());
        Map<String,List<ProcessDefinition>> map = getProcessDefInfo(definedList);

        List<ActHiTask> resultList = new ArrayList<>();
        for (HistoricTaskInstance historicTaskInstance : historicProcessInstances) {
            ActHiTask actHiTask = new ActHiTask();
            HistoricTaskInstanceEntity entity = (HistoricTaskInstanceEntity)historicTaskInstance;
            RxUtil.copyProperties(historicTaskInstance,actHiTask);
            actHiTask.setBid(historicTaskInstance.getId());
            actHiTask.setDescription(entity.getName());
            actHiTask.setTaskDefKey(entity.getTaskDefinitionKey());
            assignInstanceInfo(actHiTask,entity.getProcessDefinitionId(),map);

            // actHiInstance.setVer();
            resultList.add(actHiTask);
        }

        page.setRecords(resultList);
        return page;
    }

    @Override
    public Object processFinishPage(ActRuInstance data) {
        int maxResults = data.getPageSize();
        int firstPage = maxResults * (data.getPageNum()-1);
        HistoricProcessInstanceQuery historicProcessInstanceQuery = historyService.createHistoricProcessInstanceQuery();
        historicProcessInstanceQuery.completed();

        long count = historicProcessInstanceQuery.count();
        Page page = createPage(count,data);
        if (count < 1){
            return page;
        }
        List<HistoricProcessInstance> historicProcessInstances = historicProcessInstanceQuery.listPage(firstPage, maxResults);
        if (EmptyChecker.isEmpty(historicProcessInstances)){
            return page;
        }

        List<ActHiInstance> resultList = new ArrayList<>();
        for (HistoricProcessInstance historicProcessInstance : historicProcessInstances) {
            ActHiInstance actHiInstance = new ActHiInstance();
            HistoricProcessInstanceEntity entity = (HistoricProcessInstanceEntity)historicProcessInstance;
            RxUtil.copyProperties(historicProcessInstance,actHiInstance);
            actHiInstance.setBid(historicProcessInstance.getId());
            actHiInstance.setVersion(entity.getProcessDefinitionVersion());
           // actHiInstance.setVer();
            resultList.add(actHiInstance);
        }

        page.setRecords(resultList);
        return page;
    }
}
